package ec.fugu.amf;

import java.io.DataInputStream;
import java.io.IOException;
import java.util.Date;
import java.util.Hashtable;

/**
 * Deserialize an AMF0 Stream
 * @author deathUser
 *
 */
public class Amf0Deserializer extends AmfDeserializer {

	/**
	 * Constructor
	 * @param DataInputStream stream from HTTP Connection
	 */
	public Amf0Deserializer(DataInputStream stream) {
		super(stream);
	}

	/**
	 * readAmdData - Reads from the InputStream diferent type of Data, if forceType is not null, forces the use of the Read Method 
	 * @param Byte forceType 
	 */
	public Object readAmfData(Byte forceType) throws IOException {
		byte type;
		if(forceType==null){
			type = this.stream.readByte();
		} else {
			type = forceType.byteValue();
		}
		switch(type){
			case Amf0Constants.DT_NUMBER: return new Double(this.stream.readDouble());
			case Amf0Constants.DT_BOOL: return new Boolean(this.stream.readBoolean());
			case Amf0Constants.DT_STRING: return this.stream.readUTF();
			case Amf0Constants.DT_OBJECT: return this.readObject();
			case Amf0Constants.DT_NULL: return null;
			case Amf0Constants.DT_UNDEFINED: return null;
			//case Amf0Constants.DT_REFERENCE: return this.readReference();
			case Amf0Constants.DT_MIXEDARRAY: return this.readHashtable();
			case Amf0Constants.DT_ARRAY: return this.readArray();
			case Amf0Constants.DT_DATE: return this.readDate();
			case Amf0Constants.DT_UNSUPPORTED: return null;
			case Amf0Constants.DT_XML: return this.stream.readUTF();
			case Amf0Constants.DT_TYPEDOBJECT: return this.readTypedObject();
			//case Amf0Constants.DT_AMF3: return this.ReadAmf3();
				default: throw new IOException("Unsuported type 0x"+Integer.toHexString(new Integer(type).intValue()));
		}
	}

	public ASObject readObject() throws IOException{
		//Create the object with the className from the stream
		ASObject aso = new ASObject(this.stream.readUTF());
	    //for (int i =0; i<1000; i++){
	    while(true){
			String key = this.stream.readUTF();
			byte type = this.stream.readByte();
			if(type == Amf0Constants.DT_OBJECTTERM)
				break;
			Object value = this.readAmfData(new Byte(type));
			if (value == null) {
				//Skipping NULL value
			} else {
				aso.put(key, value);
			}
	    }
	    return aso;
	}
	/**
	 * readTypedObject - Read an Object from the InputStream
	 * @return ASObject
	 * @throws IOException
	 */
	public ASObject readTypedObject() throws IOException{
		//Create the object with the className from the stream
		ASObject aso = new ASObject(this.stream.readUTF());
	    //for (int i =0; i<1000; i++){
	    while(true){
			String key = this.stream.readUTF();
			byte type = this.stream.readByte();
			if(type == Amf0Constants.DT_OBJECTTERM)
				break;
			Object value = this.readAmfData(new Byte(type));
			if (value == null) {
				//Skipping NULL value
			} else {
				aso.put(key, value);
			}
	    }
	    return aso;
	}

	/**
	 * Read an Array of Objects from the input stream
	 * @return Object[]
	 * @throws IOException
	 */
	public Object[] readArray() throws IOException{
		int length = this.stream.readInt();
		Object[] returnValue = new Object[length];
		for(int i=0; i<length; i++){
			returnValue[i]=this.readAmfData(null);
		}
		return returnValue;
	}
	
	/**
	 * Read a Hashtable from the input stream
	 * @return Hashtable
	 * @throws IOException
	 */
	public Hashtable readHashtable() throws IOException{
		Hashtable returnValue = new Hashtable();
		int highestIndex = this.stream.readInt();
		highestIndex = 0;
		//max elements of hashtable
		while(highestIndex<100){
			String key = this.stream.readUTF();
			byte varType = this.stream.readByte();
			if(varType == Amf0Constants.DT_OBJECTTERM)
				break;
			Object value = this.readAmfData(null);
			returnValue.put(key, value);
			highestIndex++;
		}
		return returnValue;
	}
	
	/**
	 * Read a Date from the input stream
	 * @return Date
	 * @throws IOException
	 */
	public Date readDate() throws IOException{
		Date returnValue;
		double timeStamp = Math.floor(this.stream.readDouble()/1000);
		int timeZone = this.stream.readShort();
		if(timeZone == 0){
			//Empty timezone
		}
		returnValue = new Date(new Double(timeStamp).longValue());
		return returnValue;
	}
}